#include "../include/transportADT.h"
#include "../include/cityDrawer.h"
#include "../include/lineaInjector.h"
#include "../include/cityDefs.h"
#include "../include/marshallADT.h"
#include "../include/seminjector.h"
#include <stdlib.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>

static coords movMap[3][4]={{{-1,0},{1,0},{0,-1},{0,1}},
				{{0,-1},{0,1},{1,0},{-1,0}},
				{{1,0},{-1,0},{0,1},{0,-1}}};

typedef struct{
	int blocksNumX;
	int blocksNumY;
}cityStruct;

//carga los parametros de la ciudad
cityStruct * loadCity(void);

//crea la matriz que representa el mapa de la ciudad
T_DataMap ** createMap(cityStruct * city);

//agrega un bus al mapa de la ciudad
char addBus(busReqT *msgData, T_DataMap ** map);

//administra el pedido recibido por la ciudad y le responde al bus si se puede mover o no
int manageMsg(busReqT *msgData, T_DataMap ** map);

//agrega y cambia la orientacion del semaforo
void manageLight(semafT *msgData, T_DataMap ** map);

//libera la estructura de la ciudad, y el mapa
void freeCity(T_DataMap ** mapa, cityStruct * city);

//agrega una parada en el mapa, devolviendo el numero absoluto de la parada en la ciudad
void addStop(marshallADT trans, stops_t *msgData, T_DataMap ** map);

//ingresa un pasajero en la cola de pasajeros para el log
void logPasenger(passData_T *msgData, queueADT pasQueue);

//variables para la sincronizacion del thread de graficacion
pthread_mutex_t mutexDrawer;
pthread_mutex_t mutexDrawer2;
pthread_cond_t mutexDrawAvailable;

//para el manejo de señales
int exitFlag = 0;
static void
exitSigInt(int sign) {
	exitFlag = 1;
}

int
main(void){

	pid_t pidInj, pidSem;
	marshallADT trans;
	cityStruct *city;
	T_DataMap ** map;
	queueADT pasQueue;

	pthread_t drawerThread;
	int rc;
	drawT drawArg;

	//levanto la estructura ciudad
	city=loadCity();
	//creo el mapa a partir de la ciudad levantada
	map=createMap(city);

	//arranco la cola con los mensajes de pasajeros que voy a compartir con
	//el graficador
	pasQueue = openPQueue(sizeof(passData_T));

	//inicializo los mutex que voy a usar con el graficador
	pthread_mutex_init(&mutexDrawer, NULL);
	pthread_mutex_init(&mutexDrawer2, NULL);
	pthread_cond_init (&mutexDrawAvailable, NULL);

	//cargo los parametros para el drawer
	drawArg.blocksNumX = city->blocksNumX;
	drawArg.blocksNumY = city->blocksNumY;
	drawArg.map = map;
	drawArg.logQueue = pasQueue;

	//handler de la señal ctrl+c para cerrar bien el programa
	static struct sigaction act;
	act.sa_handler = exitSigInt;
	sigfillset(&(act.sa_mask));
	sigaction(SIGINT, &act, NULL);

	//abro la comunicacion
	if((trans = openMarsh(CITY_ID)) == NULL)
		kill(0, SIGUSR1);

	//hago lock del 2do mutex antes de crear al thread del graficador
	//para sincronizarlo con la ciudad
	pthread_mutex_lock(&mutexDrawer2);
	if (pthread_create(&drawerThread, NULL, cityDrawer, (void *)&drawArg) != 0) {
		fprintf(stderr, "No se pudo inicializar el graficador %d\n", rc);
		exit(-1);
	}

	//llamo al inyector de semaforos
	switch(pidSem=fork()){
			case -1: printf("Fork Semaphore Injector Failed\n");exit(1);
			case 0: semInjector(); //no vuelve
			default: break;
		}

	//llamo al inyector de lineas
	switch(pidInj=fork()){
			case -1: printf("Fork Linea Injector Failed\n");exit(1);
			case 0: lineaInjector(); //no vuelve
			default: break;
		}

	//buffer donde se guardan la data de los mensajes
	char msgData[20];
	char c;
	int code;

	//voy a usar la variable mutex en el ciclo para controlar al graficador
	pthread_mutex_lock(&mutexDrawer);
	//hago unlock del 2do mutex para que el graficador se quede esperando
	//al 1er mutex
	pthread_mutex_unlock(&mutexDrawer2);

	/*escuchar y atender pedidos de buses*/
	while(!exitFlag){
		switch(recvData(trans, (busReqT *)msgData, CITY)){
			case -1: fprintf(stderr,"Permission Denied in Receive\n");break;
			case BUS_CREATE:{c=addBus((busReqT *)msgData, map);
					sendData(trans, &c, CITY);
					break;
					}
			case BUS_MOVE:{	code= manageMsg((busReqT *)msgData, map);
					if(code==GO){
						c='G';
						sendData(trans, &c, CITY);
					}else if(code == NOGO){
						c='N';
						sendData(trans, &c, CITY);
					}
				}break;
			case TRAFFIC_L_SWITCH: manageLight((semafT *)msgData, map);break;
			case STOP: addStop(trans, (stops_t *)msgData, map);break;
			case PASSENGER: logPasenger((passData_T *)msgData, pasQueue);break;
			default: printf("Unrecognized Message Code\n");exit(1);
		}

		if(!exitFlag) {
			//dejo que el graficador actualice el mapa, durmiendome
			//sobre el 1er mutex
			pthread_cond_wait(&mutexDrawAvailable, &mutexDrawer);
			pthread_cond_signal(&mutexDrawAvailable);
		}

	}
	pthread_mutex_unlock(&mutexDrawer);
	//el graficador pudo haber quedado en wait, asi que lo despierto
	pthread_cond_signal(&mutexDrawAvailable);

	//le digo al graficador que cierre todo
	pthread_kill(drawerThread, SIGUSR1);

	void *status;
	//espero que el thread graficador termine
	pthread_join(drawerThread, &status);

	//destruyo las variables mutex
	pthread_mutex_destroy(&mutexDrawer);
	pthread_mutex_destroy(&mutexDrawer2);
	pthread_cond_destroy(&mutexDrawAvailable);

	//cierro la comunicacion
	closeMarsh(trans);

	//libero la cola de logs de pasajeros
	freePQueue(pasQueue);

	//libero el mapa y la ciudad
	freeCity(map, city);

	//envio la señal para terminar a todos mis hijos
	kill(0, SIGUSR1);

	return 0;
}

char
addBus(busReqT *msgData, T_DataMap ** map){
	int x = msgData->curPos.x;
	int y = msgData->curPos.y;

	//si es una posicion valida, lo creo en el mapa y le aviso
	if(map[x][y].tipo==EMPTY || map[x][y].tipo==STOP) {
		map[x][y].bondi = msgData->busN;
		map[x][y].linea = msgData->busLine;
		map[x][y].tipo += BUS;

		return 'O';
	}

	return 'N';
}

void
manageLight(semafT *msgData, T_DataMap ** map){
	int x = msgData->pos.x;
	int y = msgData->pos.y;

	map[x][y].lightSense = msgData->status;

	//veo si hay un bondi o no junto al semaforo, no puede haber paradas donde hay semaforos
	if (map[x][y].tipo & BUS)
		map[x][y].tipo = msgData->status + BUS; //habia un bondi, sumo el tipo
	else
		map[x][y].tipo = msgData->status;

}

void
addStop(marshallADT mar, stops_t *msgData, T_DataMap ** map){
	int x = msgData->pos.x;
	int y = msgData->pos.y;
	int error = -1;
	static int numPar = 1;

	switch(map[x][y].tipo){
	//si ya habia una parada devuelvo el valor absoluto ya seteado anteriormente
	case STOP: case (STOP+BUS): sendData(mar, &(map[x][y].numParAbs), CITY_STOP);break;
	//si no habia parada, creo un valor absoluto nuevo
	case EMPTY: case BUS:{
				map[x][y].tipo |= STOP;
				map[x][y].numParAbs = numPar;

				sendData(mar, &numPar, CITY_STOP);
				numPar++;

			};break;
	//si se quiere crear en un lugar no permitido, devuelvo un error
	default: sendData(mar, &error, CITY_STOP);break;
	}
}

void
logPasenger(passData_T *msgData, queueADT pasQueue){

	addElem(pasQueue, msgData);

}

int
manageMsg(busReqT *msgData, T_DataMap ** map){

	//calculo el movimiento en el mapa a partit del movimiento relativo
	coords mov = movMap[(int)msgData->move][(int)msgData->orientation];

	int destX, destY;
	int curX, curY;
	int flag;

	curX = msgData->curPos.x;
	curY = msgData->curPos.y;

	destX = curX + mov.x;
	destY = curY + mov.y;

	switch(map[destX][destY].tipo){
		case FORBIDDEN: case BUS: flag = 0;break;
		//si hay semaforo me fijo que se pueda mover
		case TRAFFIC_LIGHT_HOR: flag = (mov.y != 0)? 0:1;break;
		case TRAFFIC_LIGHT_VER: flag = (mov.x != 0)? 0:1;break;
		case EMPTY: case STOP: flag = 1;break;
		default:flag = 0;break;		//caso en el que es un BUS mas otra cosa
	}

	//si se movio, actualizo el mapa
	if(flag){
		map[destX][destY].linea = map[curX][curY].linea;
		map[destX][destY].bondi = map[curX][curY].bondi;
		map[destX][destY].tipo += BUS;
		map[destX][destY].pasAbord = msgData->pasajeros;

		map[curX][curY].linea = EMPTY;
		map[curX][curY].bondi = EMPTY;
		map[curX][curY].tipo -= BUS;
		map[curX][curY].pasAbord = 0;

		return GO;
	}else
		return NOGO;

}

cityStruct *
loadCity(void){
	cityStruct *city;

	if((city=malloc(sizeof(cityStruct)))==NULL){
		printf("Error de memoria\n");
		exit(1);
	}

	city->blocksNumX=6;
	city->blocksNumY=6;

	return city;
}

T_DataMap **
createMap(cityStruct * city){
	int i, j, k, l;
	T_DataMap ** mapa;

	int largo=city->blocksNumY*(BLOCKWIDTH+STREETWIDTH)+STREETWIDTH+2;
	int ancho=city->blocksNumX*(BLOCKWIDTH+STREETWIDTH)+STREETWIDTH+2;

	mapa=malloc(sizeof(T_DataMap*)*largo);

	//genero un "borde" del mapa con FORBIDDEN
	for(i=0; i<largo; ++i) {
		mapa[i]=calloc(sizeof(T_DataMap), ancho);
	}

	for(i=0; i<ancho; ++i)
		mapa[0][i].tipo=mapa[largo-1][i].tipo=FORBIDDEN;

	//asigno FORBIDDEN a las posiciones de las manzanas
	for(i=STREETWIDTH+1/**/; i<largo-1 /**/; i=i+STREETWIDTH){
		mapa[i-1][0].tipo=mapa[i-1][ancho-1].tipo=FORBIDDEN;
		mapa[i][0].tipo=mapa[i][ancho-1].tipo=FORBIDDEN;
		mapa[i+1][0].tipo=mapa[i+1][ancho-1].tipo=FORBIDDEN;
		for(j=0; j<BLOCKWIDTH; ++j){
			for(k=STREETWIDTH+1/**/; k<ancho-1/**/; k=k+STREETWIDTH){
				for(l=0; l<BLOCKWIDTH; ++l){
					mapa[i+j][k+l].tipo=FORBIDDEN;
					mapa[i+j][k+l].linea=FORBIDDEN;
					mapa[i+j][k+l].bondi=FORBIDDEN;
					mapa[i+j][k+l].way=FORBIDDEN;
				}
				k=k+BLOCKWIDTH;
			}
		}
		i=i+BLOCKWIDTH;
	}
	mapa[i-1][0].tipo=mapa[i-1][ancho-1].tipo=FORBIDDEN;

	//ingreso el sentido de las calles en cada posicion valida

	for(i=1; i<ancho; i=i+3){
			for(j=1; j<largo; j++){
				if(mapa[i][j].tipo != FORBIDDEN){
					if(i%2 == 0){
						mapa[i][j].way = SOUTH;
					}else{
						mapa[i][j].way = (i!=19)? NORTH : SOUTH;
					}
				}
			}
	}

	for(j=1; j<largo; j=j+3){
				for(i=1; i<ancho; i++){
					if(mapa[i][j].tipo != FORBIDDEN){
						if(j%2 == 0){
							mapa[i][j].way = EAST;
						}else{
							mapa[i][j].way = (j!=1)? WEST : EAST;
						}
					}
				}
		}


	return mapa;
}

void
freeCity(T_DataMap ** mapa, cityStruct * city){

	int largo = city->blocksNumY * (BLOCKWIDTH+STREETWIDTH)+STREETWIDTH+2;
	int i;

	for (i = 0; i < largo; i++) {
		free(mapa[i]);
	}
	free(mapa);

	free(city);
}
